/**
 * 
 */
package jface.viewers;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;

import org.eclipse.jface.viewers.AbstractTableViewer;
import org.eclipse.jface.viewers.CellEditor.LayoutData;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerFocusCellManager;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.viewers.ViewerColumn;
import org.eclipse.jface.viewers.ViewerRow;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.TableItem;

/**

 * 
 */
public class TableViewerEditor extends ColumnViewerEditor {
	/**
	 * This viewer's table editor.
	 */
	private final TableEditor tableEditor;

	private final TableViewerFocusCellManager focusCellManager;

	private final int feature;

	/**
	 * @param viewer
	 *            the viewer the editor is attached to
	 * @param focusCellManager
	 *            the cell focus manager if one used or <code>null</code>
	 * @param editorActivationStrategy
	 *            the strategy used to decide about the editor activation
	 * @param feature
	 *            the feature mask
	 */
	TableViewerEditor(TableViewer viewer,
			TableViewerFocusCellManager focusCellManager,
			ColumnViewerEditorActivationStrategy editorActivationStrategy,
			int feature) {
		super(viewer, editorActivationStrategy, feature);
		this.feature = feature;
		tableEditor = new TableEditor(viewer.getTable());
		this.focusCellManager = focusCellManager;
	}

	/**
	 * Create a customized editor with focusable cells
	 * 
	 * @param viewer
	 *            the viewer the editor is created for
	 * @param focusCellManager
	 *            the cell focus manager if one needed else <code>null</code>
	 * @param editorActivationStrategy
	 *            activation strategy to control if an editor activated
	 * @param feature
	 *            bit mask controlling the editor
	 *            <ul>
	 *            <li>{@link ColumnViewerEditor#DEFAULT}</li>
	 *            <li>{@link ColumnViewerEditor#TABBING_CYCLE_IN_ROW}</li>
	 *            <li>{@link ColumnViewerEditor#TABBING_HORIZONTAL}</li>
	 *            <li>{@link ColumnViewerEditor#TABBING_MOVE_TO_ROW_NEIGHBOR}</li>
	 *            <li>{@link ColumnViewerEditor#TABBING_VERTICAL}</li>
	 *            </ul>
	 * @see #create(TableViewer, ColumnViewerEditorActivationStrategy, int)
	 */
	public static void create(TableViewer viewer,
			TableViewerFocusCellManager focusCellManager,
			ColumnViewerEditorActivationStrategy editorActivationStrategy,
			int feature) {
		TableViewerEditor editor = new TableViewerEditor(viewer,
				focusCellManager, editorActivationStrategy, feature);
		viewer.setColumnViewerEditor(editor);
		if (focusCellManager != null) {
			try {
				Method m = focusCellManager.getClass().getSuperclass()
						.getDeclaredMethod("init", null);
				m.setAccessible(true);
				m.invoke(focusCellManager, null);
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}
			// focusCellManager.init();
		}
	}

	/**
	 * Create a customized editor whose activation process is customized
	 * 
	 * @param viewer
	 *            the viewer the editor is created for
	 * @param editorActivationStrategy
	 *            activation strategy to control if an editor activated
	 * @param feature
	 *            bit mask controlling the editor
	 *            <ul>
	 *            <li>{@link ColumnViewerEditor#DEFAULT}</li>
	 *            <li>{@link ColumnViewerEditor#TABBING_CYCLE_IN_ROW}</li>
	 *            <li>{@link ColumnViewerEditor#TABBING_HORIZONTAL}</li>
	 *            <li>{@link ColumnViewerEditor#TABBING_MOVE_TO_ROW_NEIGHBOR}</li>
	 *            <li>{@link ColumnViewerEditor#TABBING_VERTICAL}</li>
	 *            </ul>
	 */
	public static void create(TableViewer viewer,
			ColumnViewerEditorActivationStrategy editorActivationStrategy,
			int feature) {
		create(viewer, null, editorActivationStrategy, feature);
	}

	@Override
	protected void setEditor(Control w, Item item, int columnNumber) {
		tableEditor.setEditor(w, (TableItem) item, columnNumber);
	}

	@Override
	protected void setLayoutData(LayoutData layoutData) {
		tableEditor.grabHorizontal = layoutData.grabHorizontal;
		tableEditor.horizontalAlignment = layoutData.horizontalAlignment;
		tableEditor.minimumWidth = layoutData.minimumWidth;
	}

	@Override
	public ViewerCell getFocusCell() {
		if (focusCellManager != null) {
			return focusCellManager.getFocusCell();
		}

		return super.getFocusCell();
	}

	@Override
	protected void updateFocusCell(ViewerCell focusCell,
			ColumnViewerEditorActivationEvent event) {
		// Update the focus cell when we activated the editor with these 2
		// events
		if (event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC
				|| event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL) {

			if (focusCellManager != null) {
				try {
					Method m = AbstractTableViewer.class.getDeclaredMethod(
							"getSelectionFromWidget", null);
					m.setAccessible(true);
					List l = (List) m.invoke(getViewer(), null);

					if (focusCellManager != null) {
						m = focusCellManager
								.getClass()
								.getSuperclass()
								.getDeclaredMethod("setFocusCell",
										new Class[] { ViewerCell.class });
						m.setAccessible(true);
						m.invoke(focusCellManager, new Object[] { focusCell });
					}

					if (!l.contains(focusCell.getElement())) {
						getViewer()
								.setSelection(
										new StructuredSelection(focusCell
												.getElement()));
					}

				} catch (SecurityException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}

			}
		}
	}

	@Override
	protected void processTraverseEvent(int columnIndex, ViewerRow row,
			TraverseEvent event) {
		ViewerCell cell2edit = null;

		if (event.detail == SWT.TRAVERSE_TAB_PREVIOUS) {
			event.doit = false;

			if ((event.stateMask & SWT.CTRL) == SWT.CTRL
					&& (feature & TABBING_VERTICAL) == TABBING_VERTICAL) {
				cell2edit = searchCellAboveBelow(row, getViewer(), columnIndex,
						true);
			} else if ((feature & TABBING_HORIZONTAL) == TABBING_HORIZONTAL) {
				cell2edit = searchPreviousCell(row, row.getCell(columnIndex),
						row.getCell(columnIndex), getViewer());
			}
		} else if (event.detail == SWT.TRAVERSE_TAB_NEXT) {
			event.doit = false;

			if ((event.stateMask & SWT.CTRL) == SWT.CTRL
					&& (feature & TABBING_VERTICAL) == TABBING_VERTICAL) {
				cell2edit = searchCellAboveBelow(row, getViewer(), columnIndex,
						false);
			} else if ((feature & TABBING_HORIZONTAL) == TABBING_HORIZONTAL) {
				cell2edit = searchNextCell(row, row.getCell(columnIndex),
						row.getCell(columnIndex), getViewer());
			}
		}

		System.err.println("NEXT CELL: " + cell2edit);

		if (cell2edit != null) {
			getViewer().getControl().setRedraw(false);
			ColumnViewerEditorActivationEvent acEvent = new ColumnViewerEditorActivationEvent(
					cell2edit, event);

			try {
				Method m = ColumnViewer.class
						.getDeclaredMethod(
								"triggerEditorActivationEvent",
								new Class[] { ColumnViewerEditorActivationEvent.class });
				m.setAccessible(true);
				m.invoke(getViewer(), new Object[] { acEvent });
			} catch (SecurityException e) {
				e.printStackTrace();
			} catch (NoSuchMethodException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				e.printStackTrace();
			}

			getViewer().getControl().setRedraw(true);
		}
	}

	private ViewerCell searchNextCell(ViewerRow row, ViewerCell currentCell,
			ViewerCell originalCell, ColumnViewer viewer) {
		ViewerCell rv = null;

		ViewerCell nextCell;

		if (currentCell != null) {
			nextCell = CellUtils.getNeighbor(currentCell, ViewerCell.RIGHT,
					true);
		} else {
			nextCell = row.getCell(CellUtils.getCreationIndex(row, 0));
		}

		// No endless loop
		if (originalCell.equals(nextCell)) {
			return null;
		}

		if (nextCell != null) {
			if (isCellEditable(viewer, nextCell)) {
				rv = nextCell;
			} else {
				rv = searchNextCell(row, nextCell, originalCell, viewer);
			}
		} else {
			if ((feature & TABBING_CYCLE_IN_ROW) == TABBING_CYCLE_IN_ROW) {
				rv = searchNextCell(row, null, originalCell, viewer);
			} else if ((feature & TABBING_MOVE_TO_ROW_NEIGHBOR) == TABBING_MOVE_TO_ROW_NEIGHBOR) {
				ViewerRow rowBelow = row.getNeighbor(ViewerRow.BELOW, false);
				if (rowBelow != null) {
					rv = searchNextCell(rowBelow, null, originalCell, viewer);
				}
			}
		}

		return rv;
	}

	private ViewerCell searchPreviousCell(ViewerRow row,
			ViewerCell currentCell, ViewerCell originalCell, ColumnViewer viewer) {
		ViewerCell rv = null;
		ViewerCell previousCell;

		if (currentCell != null) {
			previousCell = CellUtils.getNeighbor(currentCell, ViewerCell.LEFT,
					true);
		} else {
			if (row.getColumnCount() != 0) {
				previousCell = row.getCell(CellUtils.getCreationIndex(row,
						row.getColumnCount() - 1));
			} else {
				previousCell = row.getCell(0);
			}

		}

		// No endless loop
		if (originalCell.equals(previousCell)) {
			return null;
		}

		if (previousCell != null) {
			if (isCellEditable(viewer, previousCell)) {
				rv = previousCell;
			} else {
				rv = searchPreviousCell(row, previousCell, originalCell, viewer);
			}
		} else {
			if ((feature & TABBING_CYCLE_IN_ROW) == TABBING_CYCLE_IN_ROW) {
				rv = searchPreviousCell(row, null, originalCell, viewer);
			} else if ((feature & TABBING_MOVE_TO_ROW_NEIGHBOR) == TABBING_MOVE_TO_ROW_NEIGHBOR) {
				ViewerRow rowAbove = row.getNeighbor(ViewerRow.ABOVE, false);
				if (rowAbove != null) {
					rv = searchPreviousCell(rowAbove, null, originalCell,
							viewer);
				}
			}
		}

		return rv;
	}

	private ViewerCell searchCellAboveBelow(ViewerRow row, ColumnViewer viewer,
			int columnIndex, boolean above) {
		ViewerCell rv = null;

		ViewerRow newRow = null;

		if (above) {
			newRow = row.getNeighbor(ViewerRow.ABOVE, false);
		} else {
			newRow = row.getNeighbor(ViewerRow.BELOW, false);
		}

		try {
			if (newRow != null) {
				Method m = ColumnViewer.class.getDeclaredMethod(
						"getViewerColumn", new Class[] { int.class });
				m.setAccessible(true);
				ViewerColumn column = (ViewerColumn) m.invoke(viewer,
						new Object[] { new Integer(columnIndex) });
				m = ViewerColumn.class.getDeclaredMethod("getEditingSupport",
						null);
				m.setAccessible(true);

				EditingSupport es = (EditingSupport) m.invoke(column, null);

				if (column != null && es != null) {
					m = EditingSupport.class.getDeclaredMethod("canEdit",
							new Class[] { Object.class });
					m.setAccessible(true);
					Boolean b = (Boolean) m.invoke(es, new Object[] { newRow
							.getItem().getData() });
					if (b.booleanValue()) {
						rv = newRow.getCell(columnIndex);
					}

				} else {
					rv = searchCellAboveBelow(newRow, viewer, columnIndex,
							above);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return rv;
	}

	private boolean isCellEditable(ColumnViewer viewer, ViewerCell cell) {
		try {
			Method m = ColumnViewer.class.getDeclaredMethod("getViewerColumn",
					new Class[] { int.class });
			m.setAccessible(true);
			ViewerColumn column = (ViewerColumn) m.invoke(viewer,
					new Object[] { new Integer(cell.getColumnIndex()) });
			m = ViewerColumn.class.getDeclaredMethod("getEditingSupport", null);
			m.setAccessible(true);

			EditingSupport es = (EditingSupport) m.invoke(column, null);

			if (column != null && es != null) {
				m = EditingSupport.class.getDeclaredMethod("canEdit",
						new Class[] { Object.class });
				m.setAccessible(true);
				// return true;
				Boolean b = (Boolean) m.invoke(es,
						new Object[] { cell.getElement() });
				return b.booleanValue();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}
}
